package com.gss.security.service;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.smartcardio.ATR;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.log4j.Logger;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.dao.DataAccessException;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.authentication.encoding.ShaPasswordEncoder;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.web.servlet.ModelAndView;

import com.gss.armory.form.UserDetailForm;
import com.gss.armory.master.ApplicationUrlMappingDTO;
import com.gss.common.util.ApplicationConstants;
import com.gss.common.util.ApplicationMaps;
import com.gss.common.util.ApplicationUtilities;
import com.gss.security.domain.RoleDetailsDTO;
import com.gss.security.domain.SuperficialRolesMapping;
import com.gss.security.domain.UserDetailsDTO;
import com.gss.security.domain.UserRoleMappingDTO;
import com.gss.security.persistence.AuthenticationDAO;
import com.gss.security.web.AuthenticationController;


public class AuthenticationService implements UserDetailsManager,UserDetailsService {
	
	private Logger logger = Logger.getLogger(AuthenticationService.class);
	
	private AuthenticationDAO authenticationDAO;
	
	public AuthenticationDAO getAuthenticationDAO() {
		return authenticationDAO;
	}
	public void setAuthenticationDAO(AuthenticationDAO authenticationDAO) {
		this.authenticationDAO = authenticationDAO;
	}
	
	public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException, DataAccessException {
		
		logger.debug("inside authentication service: method (loadUserByUserName) ");
		
		UserDetailsDTO userDetails = null;
		try {
			userDetails = authenticationDAO.getDetailsForUserId(username);
		} catch (Exception e) {
			//e.printStackTrace();
			logger.error(e.getMessage());
		}
		credentialsExpired(userDetails);
		//checkCredentialsExpired(userDetails);
		return userDetails; 
	}
	
	public ModelAndView checkCredentialsExpired(UserDetailsDTO userDetails){
		
		ModelAndView modelAndView = new ModelAndView();
		
		logger.debug("inside authentication Service: method(checkCredentialsExpired): " + userDetails);
		
		if(userDetails != null){
			if(userDetails.isCredentialsExpired() == true){
				logger.debug("ENTERED");
				modelAndView.setViewName("changepasswd.htm");
			}
			else{
				modelAndView.setViewName("myapp.homepage");
			}
		}
		return modelAndView;
	}
	
	public boolean checkpassword(String s) {
		
		
		logger.debug("inside authentication service: method(checkpassword): password entered for matching:" + s);
		try{
			Pattern pattern = Pattern.compile(ApplicationConstants.REGEX_FOR_PASSWORD);
			Matcher matcher = pattern.matcher(s);
			return matcher.matches();
		}
		catch(PatternSyntaxException e){
			logger.error(e.getMessage(),e);
			return false;
		}
	}
	
	public void changePassword(String oldPassword, String newPassword) {
		UserDetailsDTO userDetails = (UserDetailsDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
		
		logger.debug("inside authentication service: method(changePassword): changing password for: " + userDetails.getUsername());
		
		String updatePwdQuery = "update UserDetailsDTO u set u.password = :newPassword, u.version = :version, " +
				"u.strUpdatedAuthor = :updatedAuthor, updatedDate = :updatedDate where u.username = :userId";
		
		try {
			authenticationDAO.updatePasswordForUser(updatePwdQuery,userDetails,newPassword);
		} catch (Exception e) {
			//e.printStackTrace();
			logger.error(e.getMessage());
		}
		userDetails.setPassword(newPassword);
	}



	public void createUser(UserDetails user) {}
	public void deleteUser(String username) {}
	public void updateUser(UserDetails user) {}
	public boolean userExists(String username) {
		return false;
	}
	
	public boolean updateUserPassword(UserDetailsDTO detailsAuthDTO){
		boolean resultOfUpdate = false;
		resultOfUpdate = authenticationDAO.updateuser(detailsAuthDTO);
		logger.debug("inside authentication service: method(updateUserPassword): Password Updated...");
		return resultOfUpdate;
	}

	public String getMailForgetPassword(UserDetailsDTO userDetailsDTO,Long newPassword){
		StringBuilder mailMessage = new StringBuilder();
		
		mailMessage.append("<br> Dear "+userDetailsDTO.getUserDisplayName() + ", <br><br>");
		mailMessage.append("Your Login Credentials are as below. <br><br>");
		mailMessage.append("Your User Id is : "+ userDetailsDTO.getUsername() +"<br><br>");
		mailMessage.append("Your Password is : "+ newPassword +"<br><br>");
		mailMessage.append("Thanks and Regards<br>DMS Administrator");
		
		return mailMessage.toString();
	}
	
	public UserDetailsDTO credentialsExpired(UserDetailsDTO userDetailsDTO){

		if(null == userDetailsDTO)
			return userDetailsDTO;
		logger.debug("inside authentication service: method(credentialsExpired): checking expiry of credentials for: " + userDetailsDTO.getUsername());
		
		String enablePasswordExpiry = null;
		Properties properties = new Properties();
		
		try {
			properties.load(AuthenticationService.class.getResourceAsStream("/armory.properties"));
			enablePasswordExpiry = properties.getProperty("enablePasswordExpiry");
		} catch (IOException e) {
			logger.debug(e.getMessage(), e);
		}
		
		if(null != enablePasswordExpiry){
			if(ApplicationConstants.ENABLE_FUNCTION.equalsIgnoreCase(enablePasswordExpiry)){
				if(userDetailsDTO.getUpdatedDate() == null){
					Date createddate = userDetailsDTO.getCreatedDate();
					Date newdate = new Date();
					
					long milliseconds1 = createddate.getTime();
					long milliseconds2 = newdate.getTime();
					long diff = milliseconds2 - milliseconds1;
					long diffdays = diff / (24 * 60 * 60 * 1000);
					logger.debug("difference of days between current date and created date: " + diffdays);
					if(diffdays > 90){
						userDetailsDTO.setCredentialsExpired(true);
					}
				}
				else if(null != userDetailsDTO.getUpdatedDate()){
					Date updateddate = userDetailsDTO.getUpdatedDate();
					Date newdate = new Date();

					long milliseconds1 = updateddate.getTime();
					long milliseconds2 = newdate.getTime();
					long diff = milliseconds2 - milliseconds1;
					long diffdays = diff / (24 * 60 * 60 * 1000);
					logger.debug("difference of days between current date and updated date: " + diffdays);			
					if(diffdays > 90){
						userDetailsDTO.setCredentialsExpired(true);
					}
				}
			}
		}
		
		
		return userDetailsDTO;
	}
	
	public void accountLocked(UserDetailsDTO detailsAuthDTO){
		logger.debug("inside authentication service: method(accountLocked): checking account is locked or not for: " + detailsAuthDTO.getUsername());
		 
		int count = 0; 
		if(null != detailsAuthDTO.getCount())
			count = detailsAuthDTO.getCount();
		
		count ++ ;
		detailsAuthDTO.setCount(count);

		logger.debug("current value of UD_COUNT in USER_DETAILS: " + count);
		
		if(count >= 5){
			detailsAuthDTO.setAccountNonLocked(false);
			//detailsDTO.setCount(0);
		}
		  try {
			authenticationDAO.updateUserDetails(detailsAuthDTO);
		} catch (Exception e) {
			logger.error(e.getMessage(),e);
		}
	}
	
	public List<ApplicationUrlMappingDTO> loadUrlMappingList(DetachedCriteria detachedCriteria) {
		List<ApplicationUrlMappingDTO> applicationUrlMappingDTOList = authenticationDAO.loadUrlMappingList(detachedCriteria);
		return applicationUrlMappingDTOList;
	}
	
	public boolean saveNewUser(UserDetailForm detailForm) {

		Collection<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
		UserDetailsDTO detailsDTO = new UserDetailsDTO();
		try {
			RoleDetailsDTO roleDetailsDTO = new RoleDetailsDTO();
			roleDetailsDTO.setAuthority(ApplicationConstants.DEFAULT_ROLE);
			authorities.add(roleDetailsDTO);
			detailsDTO.setAuthorities(authorities);

			BeanUtils.copyProperties(detailsDTO, detailForm);
		} catch (IllegalAccessException e) {
			logger.error(e.getMessage(), e);
		} catch (InvocationTargetException e) {
			logger.error(e.getMessage(), e);
		}
		ApplicationUtilities.populateCommonDetails(detailsDTO, true);
		
		detailsDTO.setAccountNonExpired(true);
		detailsDTO.setAccountNonLocked(true);
		detailsDTO.setCredentialsNonExpired(true);
		detailsDTO.setEnabled(true);
		
		PasswordEncoder encoder = new ShaPasswordEncoder(256);
		String encPassword = encoder.encodePassword(detailForm.getPassword(), null);
		detailsDTO.setPassword(encPassword);
		

		if (authenticationDAO.saveNewUser(detailsDTO)) {
			loadUserMap();
			return true;
		} else {
			return false;
		}

	}
	
	public List<RoleDetailsDTO> loadCompleteRoleList() {
		try {
			return authenticationDAO.loadCompleteRolesList();
		} catch (Exception e) {
			logger.error(e.getMessage(),e);
			return null;
		}
	}
	
	public boolean assignRoleToNewUser(UserRoleMappingDTO detailsDTO) {
		String[] roles = detailsDTO.getRoles();
		logger.debug("inside method(assignRoleToNewUser): Number of roles alloted to user: " + roles.length);
		
		
		UserRoleMappingDTO mappingDTO = new UserRoleMappingDTO();
		boolean resultOfRecordEntry = false;
		boolean resultOfDelete = false;
		List<String> previousRoleList = new ArrayList<String>();
		Collection<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();

		List<String> previousListToCompare = new ArrayList<String>();
		List<String> newListToCompare = new ArrayList<String>();
		Set<String> tempSet = new HashSet<String>();
		Set<String> rolesToInsert = new HashSet<String>();
		
		DetachedCriteria detachedCriteria = DetachedCriteria.forClass(UserDetailsDTO.class);
		detachedCriteria.add(Restrictions.eq("username", detailsDTO.getUserId()));
		List<UserDetailsDTO> userDetailsDTO = (List<UserDetailsDTO>) authenticationDAO.loadAllBySearchCriteria(detachedCriteria);
		authorities = (List<GrantedAuthority>) userDetailsDTO.get(0).getAuthorities();

		for (GrantedAuthority authority : authorities) {
			RoleDetailsDTO detailsDTO1 = (RoleDetailsDTO) authority;
			previousRoleList.add(detailsDTO1.getRoleId());
		}
		
		//deleting previous entries in the role mapping table
		for (String roleToDelete : previousRoleList) {
			if (roleToDelete.equalsIgnoreCase(ApplicationConstants.DEFAULT_ROLE)) {
				logger.debug("Ignored Role: " + roleToDelete);
				continue;
			}
			logger.debug("Role to delete: " + roleToDelete + "  :for UserId: "+ detailsDTO.getUserId());
			mappingDTO.setRoleId(roleToDelete);
			mappingDTO.setUserId(detailsDTO.getUserId());
			resultOfDelete = authenticationDAO.deletePreviousRolesOfUser(mappingDTO);
			previousListToCompare.add(roleToDelete);
		}

		//inserting new entries in the role mapping table
		for (String roleToInsert : roles) {
			if(!rolesToInsert.equals(ApplicationConstants.DEFAULT_ROLE))
				rolesToInsert.add(ApplicationConstants.DEFAULT_ROLE);
			rolesToInsert.add(roleToInsert);
		}
		
		for(String roleToInsert : rolesToInsert) {
			logger.debug("Role to insert: " + roleToInsert + "  :for userId: "+ detailsDTO.getUserId());
			mappingDTO.setRoleId(roleToInsert);
			mappingDTO.setUserId(detailsDTO.getUserId());
			resultOfRecordEntry = authenticationDAO.updateUserRoleMappingDetails(mappingDTO);
			newListToCompare.add(roleToInsert);
		}
		
		if(null != previousListToCompare){
			previousListToCompare.add(ApplicationConstants.DEFAULT_ROLE);

			for(String tempRole : previousListToCompare){
				if(!newListToCompare.contains(tempRole)){
					tempSet.add(tempRole);
				}
			}
		}
		
		logger.debug("Roles to delete from superficial table: " + tempSet.toString());
		
		//deleting entries from the superficial table which have to be removed
		List<SuperficialRolesMapping> superficialObjectList = new ArrayList<SuperficialRolesMapping>();
		for(String tempRole : tempSet){
			DetachedCriteria superficialCriteria = DetachedCriteria.forClass(SuperficialRolesMapping.class);
			superficialCriteria.add(Restrictions.eq("userId", detailsDTO.getUserId()));
			superficialCriteria.add(Restrictions.eq("roleId", tempRole));
			superficialObjectList = authenticationDAO.loadAllBySearchCriteria(superficialCriteria);
		}
		logger.debug("Number of rows from superficial role mapping table for " + detailsDTO.getUserId() + " are: " + superficialObjectList.size());
		
		for(SuperficialRolesMapping superficialRoleObject : superficialObjectList){
			boolean resultOfSuperficialDelete = false;
			resultOfSuperficialDelete = authenticationDAO.delete(superficialRoleObject);
			logger.debug("Row for Superficial entry for " + superficialRoleObject.getUserId() + " is deleted");
		}
		
		//deleting activity mapping from the activity table
		
		
		return resultOfRecordEntry;
	}
	
	public UserDetailsDTO getUserForProvidedUserId(String userId) {
		try {
			UserDetailsDTO UserDetailsDTO = authenticationDAO.getDetailsForUserId(userId);
			return UserDetailsDTO;
		} catch (Exception e) {
			logger.error(e.getMessage(),e);
			return null;
		}
	}
	
	public void loadUserMap(){
		List<UserDetailsDTO> userList = authenticationDAO.loadAll(UserDetailsDTO.class);
		if(null != userList && userList.size() > 0){
			for(UserDetailsDTO userDetails : userList){
				ApplicationMaps.USERID_USERNAME_MAP.put(userDetails.getUsername(),userDetails.getUserDisplayName());
				ApplicationMaps.USERID_EMAIL_MAP.put(userDetails.getUsername(), userDetails.getEmailId());
				ApplicationMaps.USERID_USER_MAP.put(userDetails.getUsername(), userDetails);
			}
			logger.debug("Number of registered users in system: " + ApplicationMaps.USERID_USERNAME_MAP.size());
		}
	}
	
	public List<UserDetailsDTO> loadAllUsers() {
		List<UserDetailsDTO> usersList = null;
		try {
			usersList = authenticationDAO.loadAllUsers();

		} catch (Exception e) {
			logger.error(e.getMessage());
			if (null == usersList)
				usersList = new ArrayList<UserDetailsDTO>();
		}
		return usersList;
	}
	
	public List<UserDetailsDTO> loadAllUsersCompanyWise(){
		List<UserDetailsDTO> usersList = null;
		Set<UserDetailsDTO> userSet = new HashSet<UserDetailsDTO>();
		List<UserDetailsDTO> finalUsersList = new ArrayList<UserDetailsDTO>();
		UserDetailsDTO UserDetailsDTO = (UserDetailsDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
		DetachedCriteria detachedCriteria = DetachedCriteria.forClass(UserDetailsDTO.class);
		if(null != UserDetailsDTO.getCdId() && ApplicationConstants.ALL_INT != UserDetailsDTO.getCdId()){
			detachedCriteria.add(Restrictions.eq("cdId", UserDetailsDTO.getCdId()));
		}
		
		usersList = authenticationDAO.loadAllBySearchCriteria(detachedCriteria);
		for (UserDetailsDTO userDTO : usersList) {
			userSet.add(userDTO);
		}
		for(UserDetailsDTO uniqueUser : userSet) {
			finalUsersList.add(uniqueUser);
		}
		logger.debug("Size of users list acc to company with id: " + UserDetailsDTO.getCdId() + " is: " + finalUsersList.size());
		return finalUsersList;
	}
	
	public boolean saveUserDetailsAfterEditing(UserDetailsDTO userDetailsDTO) {
		boolean successFlag;
		userDetailsDTO.setAccountNonExpired(true);
		userDetailsDTO.setAccountNonLocked(true);
		userDetailsDTO.setCredentialsNonExpired(true);
		userDetailsDTO.setEnabled(true);

		Collection<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
		String[] roles = userDetailsDTO.getRole();
		if (null != roles && roles.length > 0) {
			for (String role : roles) {
				RoleDetailsDTO detailsDTO2 = new RoleDetailsDTO();
				detailsDTO2.setAuthority(role);
				authorities.add(detailsDTO2);
			}
		}
		logger.debug("authorities of user: " + authorities);
		userDetailsDTO.setAuthorities(authorities);

		if (authenticationDAO.saveEditedUSerDetails(userDetailsDTO)) {
			loadUserMap();
			successFlag = true;
		} else {
			successFlag = false;
		}
		return successFlag;
	}
	
	 public List<String> searchUserForRole(String userId, String role){
    	DetachedCriteria detachedCriteria = DetachedCriteria.forClass(UserRoleMappingDTO.class);
    	ProjectionList projectionList = Projections.projectionList();
    	projectionList.add(Projections.property("userId"));
    	detachedCriteria.setProjection(projectionList);
    	logger.debug("inside method(searchUserForRole): provided userId: " + userId + ", provided roleId: " + role);
    	  if(null != userId && !"".equals(userId)){
          	detachedCriteria.add(Restrictions.eq("userId", userId));
          }
    	if(null != role && role.length() > 0){
    		detachedCriteria.add(Restrictions.eq("roleId", role));
    	}
    	List<String> userList = null;
		try {
			userList = authenticationDAO.loadUserForRoleWF(detachedCriteria);
		} catch (Exception e) {
			logger.error(e.getMessage(),e);
		}
    	return userList;
    }
	
	public List<UserRoleMappingDTO> fetchUserForRoles(String roleId) {
		DetachedCriteria detachedCriteria = DetachedCriteria.forClass(UserRoleMappingDTO.class);
		detachedCriteria.add(Restrictions.eq("roleId", roleId));
		return authenticationDAO.usersListForRoleId(detachedCriteria);
	}
}